package common.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Class工具类
 *
 * @author 米泽鹏
 * @since 2022-12-31 下午 3:14
 */
@Slf4j
public class ClassUtil extends cn.hutool.core.util.ClassUtil {

	/**
	 * 字段缓存
	 */
	private static final Map<SerializableFunction<?, ?>, Field> CACHE = new ConcurrentHashMap<>();
	private static final String GET = "get";
	private static final String IS = "is";
	private static final String LAMBDA$ = "lambda$";

	private ClassUtil() {
		throw new AssertionError("不可实例化");
	}

	/**
	 * 根据getter方法获取字段名
	 */
	public static <T, R> String getFieldName(SerializableFunction<T, R> function) {
		Field field = getField(function);
		return field.getName();
	}

	/**
	 * 根据getter方法获取字段
	 */
	public static <T, R> Field getField(SerializableFunction<T, R> function) {
		return CACHE.computeIfAbsent(function, ClassUtil::findField);
	}

	private static <T, R> Field findField(SerializableFunction<T, R> function) {
		Field field = null;
		String fieldName = null;
		try {
			// 第1步：获取SerializedLambda
			Method method = function.getClass().getDeclaredMethod("writeReplace");
			method.setAccessible(Boolean.TRUE);
			SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
			// 第2步：implMethodName 即为Field对应的Getter方法名
			String implMethodName = serializedLambda.getImplMethodName();
			if (implMethodName.startsWith(GET) && implMethodName.length() > GET.length()) {
				fieldName = Introspector.decapitalize(implMethodName.substring(GET.length()));
			} else if (implMethodName.startsWith(IS) && implMethodName.length() > IS.length()) {
				fieldName = Introspector.decapitalize(implMethodName.substring(IS.length()));
			} else if (implMethodName.startsWith(LAMBDA$)) {
				throw new IllegalArgumentException(SerializableFunction.class.getSimpleName() + "不能传递lambda表达式，只能使用方法引用");
			} else {
				throw new IllegalArgumentException(implMethodName + "不是Getter方法引用");
			}
			// 第3步：获取的Class是字符串，并且包名是“/”分割，需要替换成“.”，才能获取到对应的Class对象
			String declaredClass = serializedLambda.getImplClass().replace("/", ".");
			Class<?> aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
			// 第4步：Spring中的反射工具类获取Class中定义的Field
			field = ReflectionUtils.findField(aClass, fieldName);
		} catch (Exception e) {
			log.error("ClassUtil中findField()方法发生异常", e);
		}
		// 第5步：如果没有找到对应的字段应该抛出异常
		if (field == null) {
			throw new NoSuchFieldError(fieldName);
		}
		return field;
	}

}
